/**
 * Copyright (C) 2011 Whisper Systems
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.appspot.eusms.database;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.util.Log;

import com.appspot.eusms.recipients.RecipientFactory;
import com.appspot.eusms.recipients.Recipients;

import org.whispersystems.textsecure.crypto.IdentityKey;
import org.whispersystems.textsecure.crypto.InvalidKeyException;
import org.whispersystems.textsecure.crypto.MasterCipher;
import org.whispersystems.textsecure.crypto.MasterSecret;
import org.whispersystems.textsecure.util.Base64;

import java.io.IOException;

public class IdentityDatabase extends Database {

    private static final Uri CHANGE_URI = Uri.parse("content://textsecure/identities");

    private static final String TABLE_NAME = "identities";
    private static final String ID = "_id";
    public static final String RECIPIENT = "recipient";
    public static final String IDENTITY_KEY = "key";
    public static final String MAC = "mac";

    public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
            " (" + ID + " INTEGER PRIMARY KEY, " +
            RECIPIENT + " INTEGER UNIQUE, " +
            IDENTITY_KEY + " TEXT, " +
            MAC + " TEXT);";

    public IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) {
        super(context, databaseHelper);
    }

    public Cursor getIdentities() {
        SQLiteDatabase database = databaseHelper.getReadableDatabase();
        Cursor cursor = database.query(TABLE_NAME, null, null, null, null, null, null);

        if (cursor != null)
            cursor.setNotificationUri(context.getContentResolver(), CHANGE_URI);

        return cursor;
    }

    public boolean isValidIdentity(MasterSecret masterSecret,
                                   long recipientId,
                                   IdentityKey theirIdentity) {
        SQLiteDatabase database = databaseHelper.getReadableDatabase();
        MasterCipher masterCipher = new MasterCipher(masterSecret);
        Cursor cursor = null;

        try {
            cursor = database.query(TABLE_NAME, null, RECIPIENT + " = ?",
                    new String[]{recipientId + ""}, null, null, null);

            if (cursor != null && cursor.moveToFirst()) {
                String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
                String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));

                if (!masterCipher.verifyMacFor(recipientId + serializedIdentity, Base64.decode(mac))) {
                    Log.w("IdentityDatabase", "MAC failed");
                    return false;
                }

                IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0);

                return ourIdentity.equals(theirIdentity);
            } else {
                return true;
            }
        } catch (IOException e) {
            Log.w("IdentityDatabase", e);
            return false;
        } catch (InvalidKeyException e) {
            Log.w("IdentityDatabase", e);
            return false;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    public void saveIdentity(MasterSecret masterSecret, long recipientId, IdentityKey identityKey) {
        SQLiteDatabase database = databaseHelper.getWritableDatabase();
        MasterCipher masterCipher = new MasterCipher(masterSecret);
        String identityKeyString = Base64.encodeBytes(identityKey.serialize());
        String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
                identityKeyString));

        ContentValues contentValues = new ContentValues();
        contentValues.put(RECIPIENT, recipientId);
        contentValues.put(IDENTITY_KEY, identityKeyString);
        contentValues.put(MAC, macString);

        database.replace(TABLE_NAME, null, contentValues);

        context.getContentResolver().notifyChange(CHANGE_URI, null);
    }

    public void deleteIdentity(long id) {
        SQLiteDatabase database = databaseHelper.getWritableDatabase();
        database.delete(TABLE_NAME, ID_WHERE, new String[]{id + ""});

        context.getContentResolver().notifyChange(CHANGE_URI, null);
    }

    public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
        return new Reader(masterSecret, cursor);
    }

    public class Reader {
        private final Cursor cursor;
        private final MasterCipher cipher;

        public Reader(MasterSecret masterSecret, Cursor cursor) {
            this.cursor = cursor;
            this.cipher = new MasterCipher(masterSecret);
        }

        public Identity getCurrent() {
            long recipientId = cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT));
            Recipients recipients = RecipientFactory.getRecipientsForIds(context, recipientId + "", true);

            try {
                String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
                String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));

                if (!cipher.verifyMacFor(recipientId + identityKeyString, Base64.decode(mac))) {
                    return new Identity(recipients, null);
                }

                IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyString), 0);
                return new Identity(recipients, identityKey);
            } catch (IOException e) {
                Log.w("IdentityDatabase", e);
                return new Identity(recipients, null);
            } catch (InvalidKeyException e) {
                Log.w("IdentityDatabase", e);
                return new Identity(recipients, null);
            }
        }
    }

    public class Identity {
        private final Recipients recipients;
        private final IdentityKey identityKey;

        public Identity(Recipients recipients, IdentityKey identityKey) {
            this.recipients = recipients;
            this.identityKey = identityKey;
        }

        public Recipients getRecipients() {
            return recipients;
        }

        public IdentityKey getIdentityKey() {
            return identityKey;
        }
    }

}
